package com.jctrip.hotel.source.service.atour.impl;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.google.common.collect.Lists;
import com.jctrip.hotel.common.util.AreaUtil;
import com.jctrip.hotel.common.util.DateUtil;
import com.jctrip.hotel.domain.entity.*;
import com.jctrip.hotel.source.client.HworldCallBackClient;
import com.jctrip.hotel.source.model.atour.AtourIncrNotifyData;
import com.jctrip.hotel.source.mq.IncrHotelRocketMqProducer;
import com.jctrip.hotel.source.service.basic.*;
import com.jctrip.hotel.source.xxl.atour.thread.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.jctrip.hotel.common.constant.RedisKeyConstant;
import com.jctrip.hotel.common.constant.XxlJobNameConstant;
import com.jctrip.hotel.common.enums.SourceEnum;
import com.jctrip.hotel.common.threadpool.XxlJobThreadPool;
import com.jctrip.hotel.common.util.JsonUtil;
import com.jctrip.hotel.common.util.RedisUtil;
import com.jctrip.hotel.domain.dao.HtHotelStdInfoDao;
import com.jctrip.hotel.source.model.atour.resp.CityData;
import com.jctrip.hotel.source.model.atour.resp.HotelData;
import com.jctrip.hotel.source.model.atour.resp.RoomInventoryData;
import com.jctrip.hotel.source.model.atour.resp.RoomRateData;
import com.jctrip.hotel.source.model.atour.resp.RoomRateData.CancelRuleDTO;
import com.jctrip.hotel.source.model.atour.resp.RoomTypeData;
import com.jctrip.hotel.source.model.wehotel.req.HotelInfoData.MapDataResponse;
import com.jctrip.hotel.source.service.atour.AtourService;
import com.jctrip.hotel.source.service.atour.AtourTaskService;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author henry
 * @Date 2023/6/3 15:14
 * @Description
 */
@Service
@Slf4j
public class AtourTaskServiceImpl implements AtourTaskService {
    private final static Integer QPS_COUNT = 1;
    private final static Long TIME_OUT = 1200L;
    /**
     * pop任务线程池
     */
    private static final XxlJobThreadPool syncAtourHotelInfoPopPool =
            new XxlJobThreadPool(XxlJobNameConstant.SYNC_ATOUR_HOTEL_INFO_POP);
    private static final XxlJobThreadPool syncAutoHotelHotspotRatesPopPool =
            new XxlJobThreadPool(XxlJobNameConstant.SYNC_AUTO_HOTEL_HOTSPOT_RATES_POP);
    private static final XxlJobThreadPool syncAtourOneRatesPopPool =
            new XxlJobThreadPool(XxlJobNameConstant.SYNC_AUTO_HOTEL_RATES_ONE_POP);
    private static final XxlJobThreadPool syncAtourOneLongerRatesPopPool =
            new XxlJobThreadPool(XxlJobNameConstant.SYNC_AUTO_HOTEL_RATES_ONE_LONGER_POP);
    private static final XxlJobThreadPool syncAtourRoomRepairPopPool =
            new XxlJobThreadPool(XxlJobNameConstant.SYNC_AUTO_HOTEL_ROOM_REPAIR_POP);
    private static final long PUSH_EXPIRY = 1 * 60;
    @Autowired
    private AtourService atourService;
    @Resource
    private HtHotelStdInfoService htHotelStdInfoService;
    @Resource
    private HtRoomTypeStdInfoService htRoomTypeStdInfoService;
    @Resource
    private HworldCallBackClient hworldCallBackClient;
    @Autowired
    private RedisUtil redisUtil;
    @Resource
    private HtHotelStdInfoDao htHotelStdInfoDao;
    @Resource
    private HtPriceCalendarService htPriceCalendarService;
    @Resource
    private HtPriceCalendarAtourService htPriceCalendarAtourService;
    @Resource
    private HtSourceInfoService htSourceInfoService;
    @Resource
    private HtHotelStdImageService htHotelStdImageService;
    @Autowired
    private IncrHotelRocketMqProducer incrHotelRocketMqProducer;

    @Override
    public void syncAtourCityIdListTaskFastPush(Integer appId, String key) {
        try {
            if (redisUtil.setENx(RedisKeyConstant.HOTEL_MAIN_ATOUR_CITYID_PUSH, PUSH_EXPIRY,
                    RedisKeyConstant.HOTEL_MAIN_ATOUR_CITYID_PUSH)) {
                List<CityData> cityDataList = atourService.getCityList(appId, key);
                redisUtil.leftPushAll(RedisKeyConstant.HOTEL_MAIN_ATOUR_CITYID_LIST,
                        cityDataList.stream().map(CityData::getCityId).collect(Collectors.toList()));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncAtourHotelIdListTaskFastPop(Integer appId, String key) {
        syncAtourHotelInfoPopPool.execute(() -> new SyncAtourHotelIdTaskThread(appId, key));
    }

    @Override
    public void syncAtourHotelIdTask(Integer appId, String key, Integer cityId) {
        try {
            List<HotelData> hotelDataList = atourService.getHotelList(appId, key, cityId);
            for (HotelData hotelData : hotelDataList) {
                HtHotelStdInfo htHotelStdInfo = new HtHotelStdInfo();
                htHotelStdInfo.setHotelId(atourService.parseInnId(hotelData.getHotelId()));
                htHotelStdInfo.setHotelName(hotelData.getName());
                htHotelStdInfo.setCityName(hotelData.getCityName());
                htHotelStdInfo.setSourceCode(SourceEnum.ATOUR.getCode());
                // 酒店品牌和编码
                htHotelStdInfo.setBrandName(hotelData.getBrand());
                htHotelStdInfo.setBrandCode(hotelData.getBrand());
                // 城市信息
                htHotelStdInfo.setCityCode(String.valueOf(cityId));
                // 经纬度
                htHotelStdInfo.setLongitude(new BigDecimal(hotelData.getLongitude()));
                htHotelStdInfo.setLatitude(new BigDecimal(hotelData.getLatitude()));
                htHotelStdInfo.setMapPointType(3);

                // 基础信息
                htHotelStdInfoService.saveOrUpdate(htHotelStdInfo,
                        new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getHotelId,
                                htHotelStdInfo.getHotelId()));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncAtourHotelInfoListTaskFastPush(Integer appId, String key) {
        try {
            if (redisUtil.setENx(RedisKeyConstant.HOTEL_MAIN_ATOUR_INFO_PUSH, PUSH_EXPIRY,
                    RedisKeyConstant.HOTEL_MAIN_ATOUR_INFO_PUSH)) {
                List<HtHotelStdInfo> hotelIdList = htHotelStdInfoService.list(
                        new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getSourceCode,
                                SourceEnum.ATOUR.getCode()).groupBy(HtHotelStdInfo::getHotelId));
                redisUtil.leftPushAll(RedisKeyConstant.HOTEL_MAIN_ATOUR_INFO_LIST,
                        hotelIdList.stream().map(HtHotelStdInfo::getHotelId).collect(Collectors.toList()));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncAtourHotelInfoListTaskFastPop(Integer appId, String key) {
        syncAtourHotelInfoPopPool.execute(() -> new SyncAtourHotelInfoTaskThread(appId, key));
    }

    @Override
    public void syncAtourHotelInfoTask(Integer appId, String key, Integer hotelId) {
        HotelData hotelData = atourService.getHotel(appId, key, hotelId);

        HtHotelStdInfo hotelInfoData = new HtHotelStdInfo();
        hotelInfoData.setSourceCode(SourceEnum.ATOUR.getCode());
        hotelInfoData.setHotelId(atourService.parseInnId(hotelData.getHotelId()));
        hotelInfoData.setHotelName(hotelData.getName());
        hotelInfoData.setHotelAddress(hotelData.getAddress());

        hotelInfoData.setHotelTel(hotelData.getTel());
        String cityName = hotelData.getCityName();
        if (StringUtils.isNotBlank(cityName) && cityName.endsWith("市")) {
            cityName = cityName.substring(0, cityName.length() - 1);
        }

        hotelInfoData.setCityName(cityName);
        hotelInfoData.setIntroduction(hotelData.getChainDesc());
        hotelInfoData.setBookStatus(1);
        hotelInfoData.setSupportForeignGuest(hotelData.getIsForeignGuest());
        MapDataResponse mapDataResponse = new MapDataResponse();
        mapDataResponse.setLag(Double.parseDouble(hotelData.getLatitude()));
        mapDataResponse.setLng(Double.parseDouble(hotelData.getLongitude()));
        // 不知道是什么哈哈todo
        //        mapDataResponse.setMapType(3);

        // 基础信息
        htHotelStdInfoService.saveOrUpdate(hotelInfoData,
                new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getHotelId, hotelInfoData.getHotelId()));
        // 房型
        List<RoomTypeData> roomTypeList = atourService.getRoomTypeList(appId, key, hotelId);
        for (RoomTypeData roomTypeData : roomTypeList) {
            // 房型
            HtRoomTypeStdInfo roomType = new HtRoomTypeStdInfo();
            roomType.setHotelId(hotelInfoData.getHotelId());
            roomType.setRoomTypeCode(String.valueOf(roomTypeData.getRoomTypeId()));
            roomType.setRoomTypeName(roomTypeData.getRoomTypeName());
            roomType.setRoomArea(AreaUtil.parseRoomArea(roomTypeData.getRoomArea()));
            roomType.setMaxCheckIn(roomTypeData.getLiveNum());
            roomType.setFloor(roomTypeData.getFloor());
            int bedType = 0;
            if (StringUtils.isNotBlank(roomTypeData.getBedRemark())) {
                switch (roomTypeData.getBedRemark()) {
                    case "大床":
                        bedType = 0;
                        break;
                    case "双床":
                        bedType = 2;
                        break;

                }
            }
            roomType.setBedType(bedType);
            if (StringUtils.isNotBlank(roomTypeData.getIsAddBed())) {
                roomType.setAddBed(roomTypeData.getIsAddBed().equals("1") ? 1 : 0);
            }
            if (StringUtils.isNotBlank(roomTypeData.getIsWindow())) {
                //窗户类型0=无窗；1=有窗；2=部分有窗；3=内窗；4=部分内窗
                Integer window = null;
                switch (roomTypeData.getIsWindow()) {
                    case "hisWindow":
                        window = 1;
                        break;
                    case "notWindow":
                        window = 0;
                        break;
                    case "partWindow":
                        window = 2;
                        break;
                    case "inWindow":
                        window = 3;
                        break;
                    case "skyWindow":
                        window = 1;
                        break;
                    case "closureWindow":
                        window = 1;
                        break;
                    case "bayWindow":
                        window = 1;
                        break;
                    default:
                }
                roomType.setWindowType(window);
            }
            htRoomTypeStdInfoService.saveOrUpdate(roomType,
                    new LambdaQueryWrapper<HtRoomTypeStdInfo>().eq(HtRoomTypeStdInfo::getHotelId, roomType.getHotelId())
                            .eq(HtRoomTypeStdInfo::getRoomTypeCode, roomType.getRoomTypeCode()));
        }

    }

    @Override
    public void syncAtourRatesTaskPush() {
        try {
            List<HtHotelStdInfo> htHotelStdInfos = htHotelStdInfoDao.selectList(
                    new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getSourceCode,
                            SourceEnum.ATOUR.getCode()));
            if (CollectionUtils.isNotEmpty(htHotelStdInfos)
                    && redisUtil.lGetListSize(RedisKeyConstant.HOTEL_MAIN_ATOUR_RATES_LIST) <= 0) {
//                redisUtil.leftPushAll(RedisKeyConstant.HOTEL_MAIN_ATOUR_RATES_LIST,
//                        htHotelStdInfos.stream().map(HtHotelStdInfo::getHotelId).collect(Collectors.toList()));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncAtourLongerRatesTaskPush() {
        try {
            List<HtHotelStdInfo> htHotelStdInfos = htHotelStdInfoDao.selectList(
                    new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getSourceCode,
                            SourceEnum.ATOUR.getCode()));
            if (CollectionUtils.isNotEmpty(htHotelStdInfos)
                    && redisUtil.lGetListSize(RedisKeyConstant.HOTEL_MAIN_ATOUR_LONGER_RATES_LIST) <= 0) {
//                redisUtil.leftPushAll(RedisKeyConstant.HOTEL_MAIN_ATOUR_LONGER_RATES_LIST,
//                        htHotelStdInfos.stream().map(HtHotelStdInfo::getHotelId).collect(Collectors.toList()));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncAtourRatesTaskFastPop(Integer appId, String key, Integer mebId, Integer days) {
//        syncAutoHotelRatesPopPool.execute(() -> new SyncAtourRatesTaskThread(appId, key, mebId, days));
    }

    @Override
    public void syncAtourLongerRatesTaskFastPop(Integer appId, String key, Integer mebId, Integer addDays, Integer days) {
//        syncAutoHotelLongerRatesPopPool.execute(
//                () -> new SyncAtourLongerRatesTaskThread(appId, key, mebId, addDays, days));
    }

    @Override
    public void syncAtourHotspotRatesTaskPush() {
        if (redisUtil.setENx(RedisKeyConstant.HOTEL_MAIN_ATOUR_HOTSPOT_RATES_PUSH, PUSH_EXPIRY,
                RedisKeyConstant.HOTEL_MAIN_ATOUR_HOTSPOT_RATES_PUSH)) {
            try {
                List<String> hotels = hworldCallBackClient.getHotspotHotels().stream()
                        .filter(e -> e.startsWith(SourceEnum.ATOUR.getCode())).collect(Collectors.toList());
                if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(hotels)
                        && redisUtil.lGetListSize(RedisKeyConstant.HOTEL_MAIN_ATOUR_HOTSPOT_RATES_LIST) <= 0) {
                    redisUtil.leftPushAll(RedisKeyConstant.HOTEL_MAIN_ATOUR_HOTSPOT_RATES_LIST,
                            new ArrayList<>(hotels));
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    @Override
    public void syncAtourHotspotRatesTaskFastPop(List<String> sourceIds, Integer days) {
        List<HtSourceInfo> sourceInfos = htSourceInfoService.list(new LambdaQueryWrapper<HtSourceInfo>()
                .in(HtSourceInfo::getSourceId, sourceIds));
        syncAutoHotelHotspotRatesPopPool.execute(
                () -> new SyncAtourHotspotRatesTaskThread(sourceInfos, days));
    }

    @Override
    public void syncAtourRatesTaskPop(Integer appId, String key, Integer hotelId, String start, String end,
                                      Integer mebId) {
        // 一直重试，直到我拿到机会进行请求
        List<RoomRateData> roomRateList = new ArrayList<>();
        List<RoomInventoryData> roomInventoryList = new ArrayList<>();
        while (true) {
            if (StringUtils.isNotBlank(redisUtil.acquireToken(RedisKeyConstant.HOTEL_MAIN_ATOUR_ROOM_STATUS_LIMIT_LOCK,
                    QPS_COUNT,
                    TIME_OUT))) {
                roomRateList =
                        atourService.getRoomRateList(appId, key, hotelId, null, start, end, null, mebId);
                roomInventoryList =
                        atourService.getRoomInventoryList(appId, key, hotelId, null, start, end, null, mebId);
                break;
            }
        }

        if (CollectionUtils.isEmpty(roomRateList)) {
            return;
        }
        if (CollectionUtils.isEmpty(roomInventoryList)) {
            return;
        }
        List<RoomInventoryData> finalRoomInventoryList = roomInventoryList;
        roomRateList.forEach(roomRateData -> {
            try {
                RoomInventoryData inventoryData = finalRoomInventoryList.stream().filter(roomInventoryData ->
                        roomInventoryData.getRoomTypeId().equals(roomRateData.getRoomTypeId())
                                && roomInventoryData.getAccDate().equals(roomRateData.getAccDate())).findFirst().get();

                HtPriceCalendar htPriceCalendar = new HtPriceCalendar();
                htPriceCalendar.setHotelId(atourService.parseInnId(hotelId));
                htPriceCalendar.setRoomTypeCode(String.valueOf(roomRateData.getRoomTypeId()));
                htPriceCalendar.setRoomTypeName(roomRateData.getRoomTypeName());
                htPriceCalendar.setCalDate(new SimpleDateFormat("yyyy-MM-dd").parse(roomRateData.getAccDate()));
                htPriceCalendar.setQuota(inventoryData.getInventoryNum());
                htPriceCalendar.setPurchasePrice(BigDecimal.valueOf(roomRateData.getRoomRate()));
                htPriceCalendar.setBreakfastCount(roomRateData.getBreakfastCount());
                List<CancelRuleDTO> cancelRules = roomRateData.getCancelRules();
                if (!CollectionUtils.isEmpty(cancelRules)) {
                    CancelRuleDTO cancelRuleDTO = cancelRules.get(0);
                    Integer cancelPolicyType = null;
                    switch (cancelRuleDTO.getChangeRule()) {
                        case 1:
                            cancelPolicyType = 1;
                            break;
                        case 3:
                            cancelPolicyType = 3;
                            break;
                        case 4:
                            cancelPolicyType = 5;
                            break;
                    }
                    if (cancelRuleDTO.getChargeRuleHourNum() != null) {
                        String date = new SimpleDateFormat("yyyy-MM-dd").format(htPriceCalendar.getCalDate());

                        int hours = cancelRuleDTO.getChargeRuleHourNum().intValue();
                        if (hours < 24) {
                            hours = 24 - hours;
                        } else {
                            int flag = hours % 24;
                            int days = (hours - flag) / 24;
                            hours = flag;
                            Date accDate = DateUtils.addDays(
                                    new SimpleDateFormat("yyyy-MM-dd").parse(roomRateData.getAccDate()), -days);
                            date = new SimpleDateFormat("yyyy-MM-dd").format(accDate);
                        }

                        if (hours < 10) {
                            date = date + " 0" + hours + ":00:00";
                        } else {
                            date = date + " " + hours + ":00:00";
                        }
                        htPriceCalendar.setLastCancelTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(date));
                    }
                    htPriceCalendar.setCancelPolicyType(cancelPolicyType);
                }

                htPriceCalendar.setCancelRule(JsonUtil.toString(cancelRules));
                // 保存房态信息
                htPriceCalendarService.saveOrUpdate(htPriceCalendar,
                        new LambdaQueryWrapper<HtPriceCalendar>().eq(HtPriceCalendar::getHotelId,
                                        htPriceCalendar.getHotelId())
                                .eq(HtPriceCalendar::getRoomTypeCode, htPriceCalendar.getRoomTypeCode())
                                .eq(HtPriceCalendar::getCalDate, htPriceCalendar.getCalDate()));
            } catch (Exception e) {
                log.error("syncAtourRatesTaskPop error", e);
            }

        });


    }

    @Override
    public void syncAtourPartRatesPush() {
        if (redisUtil.setENx(RedisKeyConstant.HOTEL_MAIN_ATOUR_PART_RATES_PUSH, PUSH_EXPIRY,
                RedisKeyConstant.HOTEL_MAIN_ATOUR_PART_RATES_PUSH)) {
            List<HtHotelStdInfo> htHotelStdInfos = htHotelStdInfoDao.selectList(
                    new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getSourceCode,
                            SourceEnum.ATOUR.getCode()));
            if (CollectionUtils.isNotEmpty(htHotelStdInfos)
                    && redisUtil.lGetListSize(RedisKeyConstant.HOTEL_MAIN_ATOUR_PART_RATES_LIST) <= 0) {
                redisUtil.leftPushAll(RedisKeyConstant.HOTEL_MAIN_ATOUR_PART_RATES_LIST,
                        htHotelStdInfos.stream().map(HtHotelStdInfo::getHotelId).collect(Collectors.toList()));
            }
        }

    }

    @Override
    public void syncAtourPartLongerRatesPush() {
        if (redisUtil.setENx(RedisKeyConstant.HOTEL_MAIN_ATOUR_PART_LONGER_RATES_PUSH, PUSH_EXPIRY,
                RedisKeyConstant.HOTEL_MAIN_ATOUR_PART_LONGER_RATES_PUSH)) {
            List<HtHotelStdInfo> htHotelStdInfos = htHotelStdInfoDao.selectList(
                    new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getSourceCode,
                            SourceEnum.ATOUR.getCode()));
            if (CollectionUtils.isNotEmpty(htHotelStdInfos)
                    && redisUtil.lGetListSize(RedisKeyConstant.HOTEL_MAIN_ATOUR_PART_LONGER_RATES_LIST) <= 0) {
                redisUtil.leftPushAll(RedisKeyConstant.HOTEL_MAIN_ATOUR_PART_LONGER_RATES_LIST,
                        htHotelStdInfos.stream().map(HtHotelStdInfo::getHotelId).collect(Collectors.toList()));
            }
        }
    }

    @Override
    public void syncAtourPartRatesPop(List<String> sourceIds, Integer days) {
        List<HtSourceInfo> sourceInfos = htSourceInfoService.list(new LambdaQueryWrapper<HtSourceInfo>()
                .in(HtSourceInfo::getSourceId, sourceIds));
        syncAtourOneRatesPopPool.execute(() -> new SyncAtourPartRatesTaskThread(sourceInfos, days));
    }

    @Override
    public void syncAtourPartLongerRatesPop(List<String> sourceIds, Integer addDays, Integer days) {
        List<HtSourceInfo> sourceInfos = htSourceInfoService.list(new LambdaQueryWrapper<HtSourceInfo>()
                .in(HtSourceInfo::getSourceId, sourceIds));
        syncAtourOneLongerRatesPopPool.execute(
                () -> new SyncAtourPartLongerRatesTaskThread(sourceInfos, addDays, days));
    }

    @Override
    public void syncAtourPartRates(String sourceId, Integer appId, String key, Integer hotelId, String start, String end, Integer mebId) {
        // 一直重试，直到我拿到机会进行请求
        List<RoomRateData> roomRateList;
        List<RoomInventoryData> roomInventoryList;
        while (true) {
            if (StringUtils.isNotBlank(redisUtil.acquireToken(RedisKeyConstant.HOTEL_MAIN_ATOUR_PART_ROOM_STATUS_LIMIT_LOCK,
                    QPS_COUNT,
                    TIME_OUT))) {
                roomRateList =
                        atourService.getRoomRateList(appId, key, hotelId, null, start, end, null, mebId);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                roomInventoryList =
                        atourService.getRoomInventoryList(appId, key, hotelId, null, start, end, null, mebId);
                break;
            }
        }

        if (CollectionUtils.isEmpty(roomRateList)) {
            return;
        }
        if (CollectionUtils.isEmpty(roomInventoryList)) {
            return;
        }
        // 找到当前酒店在中台数据库中的记录 先按房型码排序 再按日期排序
        List<HtPriceCalendarAtour> htPriceCalendars = htPriceCalendarAtourService.list(
                        new LambdaQueryWrapper<HtPriceCalendarAtour>()
                                .eq(HtPriceCalendarAtour::getHotelId, atourService.parseInnId(hotelId))).stream()
                .filter(e -> e.getSourceId().equals(sourceId))
                .sorted(Comparator.comparing(HtPriceCalendarAtour::getRoomTypeCode)
                        .thenComparing(HtPriceCalendarAtour::getCalDate))
                .collect(Collectors.toList());
        // 增量标记默认为false
        AtomicBoolean incrFlag = new AtomicBoolean(false);
        // 存在当前数据库中的房型
        List<String> memoryRoomTypes = htPriceCalendars.stream()
                .map(HtPriceCalendarAtour::getRoomTypeCode)
                .distinct().collect(Collectors.toList());

        try {
            // 将当前从接口中拿到的所有房型码变成一个list去跟数据库中的取差集
            List<String> tempRoomTypes = roomRateList.stream()
                    .map(RoomRateData::getRoomTypeId)
                    .map(String::valueOf)
                    .collect(Collectors.toList());
            memoryRoomTypes.removeAll(tempRoomTypes);
            if (CollectionUtils.isNotEmpty(memoryRoomTypes)) {
                incrFlag.set(true);
                memoryRoomTypes.forEach(roomTypeCode -> {
                    // 库存刷为0
                    updateQuota(atourService.parseInnId(hotelId), roomTypeCode);
                });
            }
        } catch (Exception e) {
            log.error("roomStatus-compare-error");
        }

        List<RoomInventoryData> finalRoomInventoryList = roomInventoryList;
        roomRateList.forEach(roomRateData -> {
            try {
                RoomInventoryData inventoryData = finalRoomInventoryList.stream().filter(roomInventoryData ->
                        roomInventoryData.getRoomTypeId().equals(roomRateData.getRoomTypeId())
                                && roomInventoryData.getAccDate().equals(roomRateData.getAccDate())).findFirst().get();

                HtPriceCalendarAtour htPriceCalendarAtour = new HtPriceCalendarAtour();
                htPriceCalendarAtour.setHotelId(atourService.parseInnId(hotelId));
                htPriceCalendarAtour.setRoomTypeCode(String.valueOf(roomRateData.getRoomTypeId()));
                htPriceCalendarAtour.setRoomTypeName(roomRateData.getRoomTypeName());
                htPriceCalendarAtour.setCalDate(new SimpleDateFormat("yyyy-MM-dd").parse(roomRateData.getAccDate()));
                htPriceCalendarAtour.setQuota(inventoryData.getInventoryNum());
                htPriceCalendarAtour.setPurchasePrice(BigDecimal.valueOf(roomRateData.getRoomRate()));
                htPriceCalendarAtour.setBreakfastCount(roomRateData.getBreakfastCount());
                htPriceCalendarAtour.setSourceId(sourceId);
                // 没有变化的时候才走
                if (!incrFlag.get()) {
                    // 获取当前房型 在当前日 中台的数据
                    HtPriceCalendarAtour roomCalDatePriceCalendar =
                            getRoomCalDatePriceCalendar(htPriceCalendarAtour.getRoomTypeCode(), htPriceCalendarAtour.getCalDate(), htPriceCalendars);
                    incrFlag.set(validatePrice(htPriceCalendarAtour.getRetailPrice(), htPriceCalendarAtour.getPurchasePrice(),
                            htPriceCalendarAtour.getQuota(), roomCalDatePriceCalendar));
                }
                List<CancelRuleDTO> cancelRules = roomRateData.getCancelRules();
                if (!CollectionUtils.isEmpty(cancelRules)) {
                    CancelRuleDTO cancelRuleDTO = cancelRules.get(0);
                    Integer cancelPolicyType = null;
                    switch (cancelRuleDTO.getChangeRule()) {
                        case 1:
                            cancelPolicyType = 1;
                            break;
                        case 3:
                            cancelPolicyType = 3;
                            break;
                        case 4:
                            cancelPolicyType = 5;
                            break;
                        default:
                            break;
                    }
                    if (cancelRuleDTO.getChargeRuleHourNum() != null) {
                        String date = new SimpleDateFormat("yyyy-MM-dd").format(htPriceCalendarAtour.getCalDate());

                        int hours = cancelRuleDTO.getChargeRuleHourNum().intValue();
                        if (hours < 24) {
                            hours = 24 - hours;
                        } else {
                            int flag = hours % 24;
                            int days = (hours - flag) / 24;
                            hours = flag;
                            Date accDate = DateUtils.addDays(
                                    new SimpleDateFormat("yyyy-MM-dd").parse(roomRateData.getAccDate()), -days);
                            date = new SimpleDateFormat("yyyy-MM-dd").format(accDate);
                        }

                        if (hours < 10) {
                            date = date + " 0" + hours + ":00:00";
                        } else {
                            date = date + " " + hours + ":00:00";
                        }
                        htPriceCalendarAtour.setLastCancelTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(date));
                    }
                    htPriceCalendarAtour.setCancelPolicyType(cancelPolicyType);
                }

                htPriceCalendarAtour.setCancelRule(JsonUtil.toString(cancelRules));
                // 保存房态信息
                htPriceCalendarAtourService.saveOrUpdate(htPriceCalendarAtour,
                        new LambdaQueryWrapper<HtPriceCalendarAtour>()
                                .eq(HtPriceCalendarAtour::getHotelId, htPriceCalendarAtour.getHotelId())
                                .eq(HtPriceCalendarAtour::getRoomTypeCode, htPriceCalendarAtour.getRoomTypeCode())
                                .eq(HtPriceCalendarAtour::getCalDate, htPriceCalendarAtour.getCalDate())
                                .eq(HtPriceCalendarAtour::getSourceId, sourceId));
            } catch (Exception e) {
                log.error("syncAtourRatesTaskPop error", e);
            }
        });
        if (incrFlag.get()) {
            log.info("main-incrHotel={}", atourService.parseInnId(hotelId));
            // TODO send mq
            incrHotelRocketMqProducer.send(atourService.parseInnId(hotelId));
        }
    }

    @Override
    public void syncAtourRoomRepairPush() {
        if (redisUtil.setENx(RedisKeyConstant.HOTEL_MAIN_ATOUR_ROOMS_REPAIR_PUSH, PUSH_EXPIRY,
                RedisKeyConstant.HOTEL_MAIN_ATOUR_ROOMS_REPAIR_PUSH)) {
            List<HtHotelStdInfo> htHotelStdInfos = htHotelStdInfoDao.selectList(
                    new LambdaQueryWrapper<HtHotelStdInfo>()
                            .eq(HtHotelStdInfo::getSourceCode, SourceEnum.ATOUR.getCode()));
            if (CollectionUtils.isNotEmpty(htHotelStdInfos)
                    && redisUtil.lGetListSize(RedisKeyConstant.HOTEL_MAIN_ATOUR_ROOMS_REPAIR_LIST) <= 0) {
                redisUtil.leftPushAll(RedisKeyConstant.HOTEL_MAIN_ATOUR_ROOMS_REPAIR_LIST,
                        htHotelStdInfos.stream().map(HtHotelStdInfo::getHotelId).collect(Collectors.toList()));
            }
        }
    }

    @Override
    public void syncAtourRoomRepairPop(Integer appId, String key, Integer mebId) {
        syncAtourRoomRepairPopPool.execute(
                () -> new SyncAtourRoomRepairTaskThread(appId, key, mebId));
    }

    @Override
    public void syncAtourRoomRepair(Integer appId, String key, Integer hotelId, String start, String end, Integer mebId) {
        // 一直重试，直到我拿到机会进行请求
        List<RoomInventoryData> roomInventoryList;
        // 将当前酒店下所有房型全部置为不可售
        htRoomTypeStdInfoService.update(null,
                new LambdaUpdateWrapper<HtRoomTypeStdInfo>()
                        .set(HtRoomTypeStdInfo::getRoomStatus, 0)
                        .eq(HtRoomTypeStdInfo::getHotelId, atourService.parseInnId(hotelId)));
        while (true) {
            if (StringUtils.isNotBlank(redisUtil.acquireToken(RedisKeyConstant.HOTEL_MAIN_ATOUR_ROOM_STATUS_LIMIT_LOCK,
                    QPS_COUNT,
                    TIME_OUT))) {
                roomInventoryList =
                        atourService.getRoomInventoryList(appId, key, hotelId, null, start, end, null, mebId);
                break;
            }
        }

        if (CollectionUtils.isEmpty(roomInventoryList)) {
            return;
        }
        roomInventoryList.forEach(roomInventoryData -> {
            // 将获取到房态的房型状态改为1
            htRoomTypeStdInfoService.update(null,
                    new LambdaUpdateWrapper<HtRoomTypeStdInfo>()
                            .set(HtRoomTypeStdInfo::getRoomStatus, 1)
                            .eq(HtRoomTypeStdInfo::getHotelId, atourService.parseInnId(hotelId))
                            .eq(HtRoomTypeStdInfo::getRoomTypeCode, roomInventoryData.getRoomTypeId()));
        });
    }

    @Override
    public void syncAtourPicPush() {
        if (redisUtil.setENx(RedisKeyConstant.HOTEL_MAIN_ATOUR_PIC_PUSH, PUSH_EXPIRY,
                RedisKeyConstant.HOTEL_MAIN_ATOUR_PIC_PUSH)) {
            List<String> hotelIds = htHotelStdInfoDao.selectList(new LambdaQueryWrapper<HtHotelStdInfo>()
                            .eq(HtHotelStdInfo::getSourceCode, SourceEnum.ATOUR.getCode())
                            .eq(HtHotelStdInfo::getSourceStatus, 1)
                            .eq(HtHotelStdInfo::getBookStatus, 1))
                    .stream().map(HtHotelStdInfo::getHotelId)
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(hotelIds)
                    && redisUtil.lGetListSize(RedisKeyConstant.HOTEL_MAIN_ATOUR_PIC_LIST) <= 0) {
                redisUtil.leftPushAll(RedisKeyConstant.HOTEL_MAIN_ATOUR_PIC_LIST, new ArrayList<>(hotelIds));
            }
        }
    }

    @Override
    public void syncAtourPicPop(String sourceAppId, String sourceSecret) {
        syncAtourHotelInfoPopPool.execute(() -> new SyncAtourPicTaskThread(sourceAppId, sourceSecret));
    }

    @Override
    public void syncAtourPic(String appId, String secret, String hotelId) {
        // 请求酒店，获取酒店图片。
        HotelData hotelData = atourService.getHotel(Integer.parseInt(appId), secret, atourService.parseHotelId(hotelId));
        if (hotelData == null) {
            return;
        }
        List<String> pictures = hotelData.getPictures();
        // 不存在则插入，只插入1次
        List<HtHotelStdImage> hotelImages = htHotelStdImageService.list(new LambdaQueryWrapper<HtHotelStdImage>()
                .eq(HtHotelStdImage::getHotelId, hotelId)
                .eq(HtHotelStdImage::getImageType, 1));

        if (CollectionUtils.isEmpty(hotelImages) && CollectionUtils.isNotEmpty(pictures)) {
            savePicInfo(hotelId, null, 1, pictures.get(0), "");
            // 酒店图片无法确认，全部落 2酒店形象图
            pictures.forEach(img -> {
                savePicInfo(hotelId, null, 2, img, "");
            });
        }

        // 插入房型图片
        // 房型
        List<RoomTypeData> roomTypeList = atourService.getRoomTypeList(Integer.parseInt(appId), secret, atourService.parseHotelId(hotelId));
        Optional.ofNullable(roomTypeList).orElse(new ArrayList<>())
                .forEach(roomTypeData -> {
                    List<HtHotelStdImage> roomImage = htHotelStdImageService.list(new LambdaQueryWrapper<HtHotelStdImage>()
                            .eq(HtHotelStdImage::getHotelId, hotelId)
                            .eq(HtHotelStdImage::getRoomTypeCode, roomTypeData.getRoomTypeId())
                            .eq(HtHotelStdImage::getImageType, 3));

                    if (CollectionUtils.isNotEmpty(roomImage)) {
                        return;
                    }
                    if (CollectionUtils.isEmpty(roomTypeData.getPictures())) {
                        return;
                    }
                    // 随机选择一张
                    String url = roomTypeData.getPictures().stream().findAny().orElse("");
                    savePicInfo(hotelId, String.valueOf(roomTypeData.getRoomTypeId()), 3, url, "");
                });

        try {
            Thread.sleep(500);
        } catch (InterruptedException ignore) {
        }
    }

    @Override
    @Async("callBackTaskAsyncPool")
    public void syncAtourIncrCallBackProcess(List<AtourIncrNotifyData> atourIncrNotifyDataList) {
        /**
         *  1.更新下数据库库存；
         *  2.< 3 向下游发送通信，关房处理
         *  3.>= 3的不做关房处理 , 不通知下游处理
         */
        List<HtSourceInfo> sourceInfoList = htSourceInfoService.list(new LambdaQueryWrapper<HtSourceInfo>()
                .eq(HtSourceInfo::getSourceCode, SourceEnum.ATOUR.getCode()));
        // 获取拉房态信息的membIds
        List<Integer> membIds = sourceInfoList.stream()
                .filter(e -> {
                    Boolean pullPriceFlag = (Boolean) JsonUtil.toMap(e.getExtra()).get("pullPriceFlag");
                    return pullPriceFlag != null && pullPriceFlag;
                })
                .map(e -> (Integer) JsonUtil.toMap(e.getExtra()).get("mebId"))
                .collect(Collectors.toList());

        atourIncrNotifyDataList.forEach(atourIncrNotifyData -> {
            try {
                // 更新酒店本地数据
                String hotelId = atourService.parseInnId(atourIncrNotifyData.getHotelId());

                // 按日期分类
                Map<String, List<AtourIncrNotifyData.AccDateStockParamsBean>> hotelQuotaMap = atourIncrNotifyData.getAccDateStockParams().stream()
                        .collect(Collectors.groupingBy(AtourIncrNotifyData.AccDateStockParamsBean::getAccDate));
                // 最小库存
                AtomicReference<Integer> minStock = new AtomicReference<>(9999);

                hotelQuotaMap.forEach((accDate, accDateStockList) -> {
                    // 匹配满足的库存变动信息
                    List<AtourIncrNotifyData.AccDateStockParamsBean> fillAccDateStockList = accDateStockList.stream()
                            .filter(e -> e.getMebIds().stream().anyMatch(membIds::contains))
                            .collect(Collectors.toList());

                    // 存在则进行更新
                    if (CollectionUtils.isNotEmpty(fillAccDateStockList)) {
                        Integer stock = fillAccDateStockList.get(0).getStock();
                        minStock.set(Math.min(stock, minStock.get()));
                        htPriceCalendarAtourService.update(new LambdaUpdateWrapper<HtPriceCalendarAtour>()
                                .eq(HtPriceCalendarAtour::getHotelId, hotelId)
                                .eq(HtPriceCalendarAtour::getRoomTypeCode, atourIncrNotifyData.getRoomTypeId())
                                .eq(HtPriceCalendarAtour::getCalDate, DateUtil.parseDate(accDate, "yyyy-MM-dd"))
                                .set(HtPriceCalendarAtour::getQuota, stock));
                    }
                });

                // TODO send mq
                if (minStock.get() < 3) {
                    incrHotelRocketMqProducer.send(hotelId);
                }
            } catch (Exception e) {
                log.error("syncAtourIncrCallBackProcess ,hotelId:{},roomType:{}", atourIncrNotifyData.getHotelId(), atourIncrNotifyData.getRoomTypeId());
            }
        });
    }

    private Boolean validatePrice(BigDecimal retailPrice, BigDecimal purchasePrice, Integer quota, HtPriceCalendarAtour htPriceCalendar) {
        if (Objects.isNull(htPriceCalendar)) {
            return true;
        }
        boolean flag = false;

        if (Objects.nonNull(htPriceCalendar.getQuota()) && Objects.nonNull(quota)) {
            flag = !htPriceCalendar.getQuota().equals(quota) && quota <= 10;
        }

        if (Objects.nonNull(htPriceCalendar.getRetailPrice()) && Objects.nonNull(retailPrice)) {
            flag = htPriceCalendar.getRetailPrice().compareTo(retailPrice) != 0 || flag;
        }

        if (Objects.nonNull(htPriceCalendar.getPurchasePrice()) && Objects.nonNull(purchasePrice)) {
            flag = htPriceCalendar.getPurchasePrice().compareTo(purchasePrice) != 0 || flag;
        }

        return flag;
    }

    private HtPriceCalendarAtour getRoomCalDatePriceCalendar(String roomType, Date date, List<HtPriceCalendarAtour> htPriceCalendars) {
        return htPriceCalendars.stream()
                .filter(e -> e.getRoomTypeCode().equals(roomType)
                        && DateUtils.isSameDay(e.getCalDate(), date))
                .findFirst().orElse(null);
    }

    private void updateQuota(String hotelId, String roomTypeCode) {
        htPriceCalendarAtourService.update(null, new LambdaUpdateWrapper<HtPriceCalendarAtour>()
                .set(HtPriceCalendarAtour::getQuota, 0)
                .eq(HtPriceCalendarAtour::getHotelId, hotelId)
                .eq(HtPriceCalendarAtour::getRoomTypeCode, roomTypeCode));
    }

    private void savePicInfo(String hotelId, String roomTypeCode, Integer type, String sourceUrl, String imageUrl) {
        HtHotelStdImage image = new HtHotelStdImage();
        image.setHotelId(hotelId);
        image.setRoomTypeCode(roomTypeCode);
        image.setImageType(type);
        image.setSourceUrl(sourceUrl);
        image.setImageUrl(imageUrl);
        htHotelStdImageService.saveOrUpdate(image);
    }
}
